package com.atguigu.gmall.seckill.biz.impl;
import java.math.BigDecimal;

import com.atguigu.gmall.feign.order.OrderFeignClient;
import com.atguigu.gmall.feign.user.UserFeignClient;
import com.atguigu.gmall.seckill.vo.SeckillOrderSubmitVo;
import com.atguigu.gmall.user.entity.UserAddress;
import com.google.common.collect.Lists;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.config.mq.MqService;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.execption.GmallException;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.DateUtil;
import com.atguigu.gmall.common.util.MD5;
import com.atguigu.gmall.common.util.UserAuthUtil;
import com.atguigu.gmall.mq.seckill.SeckillOrderMsg;
import com.atguigu.gmall.order.entity.OrderInfo;
import com.atguigu.gmall.seckill.biz.SeckillBizService;
import com.atguigu.gmall.seckill.entity.SeckillGoods;
import com.atguigu.gmall.seckill.service.SeckillGoodsService;
import com.atguigu.gmall.seckill.vo.SeckillOrderConfirmVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class SeckillBizServiceImpl implements SeckillBizService {

    @Autowired
    SeckillGoodsService seckillGoodsService;
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    MqService mqService;
    @Autowired
    UserFeignClient userFeignClient;
    @Autowired
    OrderFeignClient orderFeignClient;

    @Override
    public String generateSeckillCode(Long skuId) {
        //做前置校验
        //1 获取秒杀的商品数据
        SeckillGoods detail = seckillGoodsService.getDetail(skuId);
        //2 校验
        //【秒杀时间校验】
        Date current = new Date();
        if (!current.after(detail.getStartTime())){
            //秒杀还未开始
            throw new GmallException(ResultCodeEnum.SECKILL_NO_START);
        }
        if (!current.before(detail.getEndTime())){
            //秒杀已经结束
            throw new GmallException(ResultCodeEnum.SECKILL_END);
        }

        //快速校验本地库存 只需要看本地缓存（getStockCount 内存级别的库存状态位）
        if (detail.getStockCount() <= 0 ){
            //本地没有库存，数据库一定没有，本地有库存，数据库不一定有
            throw new GmallException(ResultCodeEnum.SECKILL_FINISH);
        }

        //走到这一步，才能参与秒杀，生成秒杀码
        String code = generateCode(skuId);
        log.info("生成秒杀码：{}",code);
        //再给redis缓存一下这个码，后续用户带的码和redis的码进行比对，防止一些秒杀脚本
        //一定以前没有的时候才保存秒杀码与请求次数
        redisTemplate.opsForValue().setIfAbsent(RedisConst.SECKILL_CODE+code, "1",2, TimeUnit.DAYS);

        return code;
    }

    //生成秒杀码 的算法。不要泄露
    private String generateCode(Long skuId) {
        String formatDate = DateUtil.formatDate(new Date());//当前时间
        Long userId = UserAuthUtil.getUserId();//当前用户ID
        String str = formatDate + "_" + userId + "_" + skuId;
        String code = MD5.encrypt(str);//进行加密
        return code;
    }

    //假如百万请求并发，不能都放给数据库
    @Override
    public void seckillOrder(Long skuId, String code) {

        //1 合法性校验
        SeckillGoods detail = seckillGoodsService.getDetail(skuId);
        Date date = new Date();//获取当前时间
        //校验商品是否 秒杀开始
        if (!date.after(detail.getStartTime())){
            throw new GmallException(ResultCodeEnum.SECKILL_NO_START);
        }
        //校验商品是否 秒杀结束
        if (!date.before(detail.getEndTime())){
            throw new GmallException(ResultCodeEnum.SECKILL_END);
        }
        //快速校验库存：内存说没有，一定没有
        if (detail.getStockCount()<=0){
            throw new GmallException(ResultCodeEnum.SECKILL_FINISH);
        }

        //2 秒杀码校验【双校验】：不仅客户端到来的码与算法生成的当前skuid对应的码一样，而且redis中也要有
        String generateCode = generateCode(skuId);
        if (!generateCode.equals(code)){//脚本抢单
            throw new GmallException(ResultCodeEnum.SECKILL_ILLEGAL);
        }
        //redis中是否存在:拿别的商品的码冒充此次商品的码
        if (!redisTemplate.hasKey(RedisConst.SECKILL_CODE+code)){//脚本抢单
            throw new GmallException(ResultCodeEnum.SECKILL_ILLEGAL);
        }

        //统计同一个秒杀请求数量：幂等快速扣库存，发消息
        Long increment = redisTemplate.opsForValue().increment(RedisConst.SECKILL_CODE + code);//increment默认是1
        if (increment<=2){
            //给MQ发送排队消息，并且内存库存状态 -1【快速扣库存】
            log.info("校验全部通过，准备排队：下秒杀单");
            SeckillOrderMsg orderMsg = new SeckillOrderMsg();
            orderMsg.setUserId(UserAuthUtil.getUserId());
            orderMsg.setCode(code);
            orderMsg.setSkuId(skuId);
            orderMsg.setDate(DateUtil.formatDate(new Date()));
            //发送消息，并且可以判断：用户已经发过这个消息，就不用再发下秒杀单的消息了
            mqService.send(orderMsg, MqConst.SECKILL_EVENT_EXCHANGE,MqConst.SECKILL_ORDER_RK);
        }else {
            log.info("秒杀请求已经发过{}了。。。",increment);
        }
    }

    @Override
    public ResultCodeEnum checkOrder(Long skuId) {
        //去redis中先看有没有秒杀单临时数据，如果有就说明秒杀成功
        String code = generateCode(skuId);
        String json = redisTemplate.opsForValue().get(RedisConst.SECKILL_ORDER + code);
        if ("X".equals(json)){
            //说明秒杀扣库存失败了
            return ResultCodeEnum.SECKILL_FINISH;//213
        }
        if (!StringUtils.isEmpty(json)){
            OrderInfo orderInfo = JSON.parseObject(json, OrderInfo.class);
            if (StringUtils.isEmpty(orderInfo.getDeliveryAddress())){
                //如果没有收货地址
                return ResultCodeEnum.SECKILL_SUCCESS;//215
            }else {
                return ResultCodeEnum.SECKILL_ORDER_SUCCESS;//218
            }
        }

        //redis的秒杀码
        String count = redisTemplate.opsForValue().get(RedisConst.SECKILL_CODE + code);
        if(Long.parseLong(count) >1){
            return ResultCodeEnum.SECKILL_RUN;
        }
        return ResultCodeEnum.SECKILL_FAIL;
    }

    @Override
    public SeckillOrderConfirmVo getSeckillOrderInfo(String code) {
        //查询数据库redis
        log.info("秒杀码：{}",code);
        String json = redisTemplate.opsForValue().get(RedisConst.SECKILL_ORDER + code);
        OrderInfo orderInfo = JSON.parseObject(json, OrderInfo.class);
        //准备数据
        SeckillOrderConfirmVo confirmVo = new SeckillOrderConfirmVo();
        confirmVo.setDetailArrayList(orderInfo.getOrderDetails());
        confirmVo.setTotalNum(1);
        confirmVo.setTotalAmount(orderInfo.getTotalAmount());

        //远程调用 userFeign 查询用户地址
        Long userId = UserAuthUtil.getUserId();
        List<UserAddress> data = userFeignClient.getUserAddress(userId).getData();
        confirmVo.setUserAddressList(data);

        return confirmVo;
    }

    @Override
    public Long submitOrder(SeckillOrderSubmitVo submitVo) {
        //获取到秒杀码
        String code = submitVo.getCode();
        //获取reids秒杀单数据 json
        String json = redisTemplate.opsForValue().get(RedisConst.SECKILL_ORDER + code);
        //转成orderInfo
        OrderInfo orderInfo = JSON.parseObject(json, OrderInfo.class);
        orderInfo.setConsignee(submitVo.getConsignee());
        orderInfo.setConsigneeTel(submitVo.getConsigneeTel());
        orderInfo.setDeliveryAddress(submitVo.getDeliveryAddress());
        orderInfo.setOrderComment(submitVo.getOrderComment());

        //远程调用订单服务 创建秒杀单
        Long orderId = orderFeignClient.saveSeckillOrder(orderInfo).getData();

        //保存到redis
        redisTemplate.opsForValue().set(RedisConst.SECKILL_ORDER+code, JSON.toJSONString(orderInfo));

        return orderId;
    }
}
